/*
 * graphics.c
 */

#include "graphics.h"
#include "texture.h"
#include <agent/player.h>
#include <gamelogic/npc.h>
#include <gamelogic/npc_lua.h>
#include <gui/gui.h>
#include <lua/luaR.h>
#include <map/map_file.h>
#include <map/map_handler.h>
#include <rowgage.h>

#include <stdio.h>

#ifdef __APPLE_CC__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

static int fps = 30;
static int lastFrame = 0;
static unsigned int window_width = 500;
static unsigned int window_height = 500;

static int grass_texture;
static int desert_texture;
static int player_texture;
static int cat_texture;
static void (*draw_alt)(void) = NULL;
static int alternate_draw = 0;

/*
 * Function called whenever the window is resized.
 * TODO: Stretch the view (cut-off?) after a certain point.
 */
void reshape(int w, int h) {
    window_width = w;
    window_height = h;
    glViewport(0, 0, w, h);
    GLfloat aspect = (GLfloat)w / (GLfloat)h;
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, window_width, 0, window_height);
}

/*
 * Draws a single tile at the given x/y coordinate with the given texture ID.
 * TODO: Turn texture into separate typedef instead of int.
 */
static inline void draw_map_tile( unsigned int x, unsigned int y, int texture ) {
    struct texture t;
    t = texture_get_terrain(texture);
    glPushMatrix();
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, t.id);
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    glBegin(GL_QUADS);
        glTexCoord2f( 0.0f, 0.0f );
        glVertex2f( x, y );

        glTexCoord2f( 1.0f, 0.0f );
        glVertex2f( x + t.width, y );

        glTexCoord2f( 1.0f, 1.0f );
        glVertex2f( x + t.width, y + t.height );

        glTexCoord2f( 0.0f, 1.0f );
        glVertex2f( x, y + t.height );
    glEnd();

    glDisable(GL_TEXTURE_2D);
    glPopMatrix();
}

/*
 * Draws the terrain around the player.
 */
void drawTerrain() {
    unsigned int x;
    unsigned int y;
    unsigned int tile_x;
    unsigned int tile_y;
    unsigned int tiles; /* # tiles per side on screen */
    unsigned int i;
    unsigned int j;
    struct map_tile current;
    tiles = 30;
    x = player_get_x();
    y = player_get_y();
    glPushMatrix();
    glTranslatef( -1l * (long)x, -1 * (long)y, 0.0f);
    glPushMatrix();
    glTranslatef( window_width / 2, window_height / 2, 0.0f );
    tile_x = (x / 32) - (tiles / 2);
    tile_y = (y / 32) - (tiles / 2);
    for( i = 0; i < tiles; i++ ) {
        for( j = 0; j < tiles; j++ ) {
            x = (i + tile_x) * 32;
            y = (j + tile_y) * 32;
            if( map_in_range(i + tile_x, j + tile_y) ) {
                current = get_tile(i + tile_x, j + tile_y);
                draw_map_tile( x, y, (int)current.type );
            }
        }
    }
    glPopMatrix();
    glPopMatrix();
}

/*
 * Draws a single NPC at the given x/y coordinate with the given texture ID.
 * TODO: Turn texture into separate typedef instead of int.
 */
static inline void draw_npc( unsigned int x, unsigned int y, int texture ) {
    struct texture t;
    t = texture_get_unit(texture);
    glPushMatrix();
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, t.id);
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    glBegin(GL_QUADS);
        glTexCoord2f( 0.0f, 0.0f );
        glVertex2f( x, y );

        glTexCoord2f( 1.0f, 0.0f );
        glVertex2f( x + t.width, y );

        glTexCoord2f( 1.0f, 1.0f );
        glVertex2f( x + t.width, y + t.height );

        glTexCoord2f( 0.0f, 1.0f );
        glVertex2f( x, y + t.height );
    glEnd();

    glDisable(GL_TEXTURE_2D);
    glPopMatrix();
}

/*
 * Draws the objects (including NPCs) visible to the player.
 */
void drawObjects() {
    unsigned int x;
    unsigned int y;
    unit *u;
    x = player_get_x();
    y = player_get_y();
    glPushMatrix();
    glTranslatef( -1l * (long)x, -1 * (long)y, 0.0f);
    glPushMatrix();
    glTranslatef( window_width / 2, window_height / 2, 0.0f );
    npc_list_reset();
    while( u = npc_next() ) {
        if( x >= u->x ) {
            if( y >= u->y ) {
                if( x - u->x <= 500 && y - u->y <= 500 ) {
                    draw_npc(u->x, u->y, cat_texture);
                }
            }
            else {
                if( x - u->x <= 500 && u->y - y <= 500 ) {
                    draw_npc(u->x, u->y, cat_texture);
                }
            }
        }
        else {
            if( y >= u->y ) {
                if( u->x - x <= 500 && y - u->y <= 500 ) {
                    draw_npc(u->x, u->y, cat_texture);
                }
            }
            else {
                if( u->x - x <= 500 && u->y - y <= 500 ) {
                    draw_npc(u->x, u->y, cat_texture);
                }
            }
        }
    }
    glPopMatrix();
    glPopMatrix();
}

/*
 * Draws the player at the center of the screen.
 */
void drawPlayer() {
    struct texture t;
    t = texture_get_unit(player_texture);
    glPushMatrix();
    glTranslatef(window_width / 2, window_height / 2, 0.0f);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, t.id);
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f);
        glVertex2f(0.0f, 0.0f);

        glTexCoord2f(1.0f, 0.0f);
        glVertex2f(0.0f + t.width, 0.0f);

        glTexCoord2f(1.0f, 1.0f);
        glVertex2f(0.0f + t.width, 0.0f + t.height);

        glTexCoord2f(0.0f, 1.0f);
        glVertex2f(0.0f, t.height);
    glEnd();
    glDisable(GL_TEXTURE_2D);
    glPopMatrix();
}

/*
 * Check OpenGL for errors.
 * TODO: Integrate with rest of error reporting.
 */
void graphics_check_errors() {
    GLenum err;
    err = glGetError();
    switch(err) {
        default:
        case GL_NO_ERROR: break;
        case GL_INVALID_ENUM: printf("invalid enum\n"); break;
        case GL_INVALID_VALUE: printf("invalid value\n"); break;
        case GL_INVALID_OPERATION: printf("invalid operation\n"); break;
        case GL_STACK_OVERFLOW: printf("stack overflow\n"); break;
        case GL_STACK_UNDERFLOW: printf("stack underflow\n"); break;
        case GL_OUT_OF_MEMORY: printf("out of memory\n"); break;
    }
}

/*
 * Main display function. Graphics are double-buffered, so all drawing is done
 * on the back buffer, then buffers are swapped.
 */
void display() {
    glClear(GL_COLOR_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glDisable(GL_DEPTH_TEST);

    if( draw_alt == NULL || alternate_draw == 0 ) {
        drawTerrain();
        drawObjects();
        drawPlayer();
        drawGUI();
    }
    else {
        draw_alt();
    }
    graphics_check_errors();

    glFlush();
    glutSwapBuffers();
}

/*
 * Acts as our animation idle function. Checks to make sure the FPS doesn't
 * exceed a certain given amount, and fires the next event in the controller
 * queue.
 */
void nextAnimationFrame() {
    /* First, we grab the current time. */
    int newFrame = glutGet(GLUT_ELAPSED_TIME);
    /* The if statement caps the frame rate; we need 1000 because we get */
    /* elapsed time in milliseconds. */
    if ((newFrame - lastFrame) >= (1000 / fps)) {
        /* The new frame becomes the last frame. */
        lastFrame = newFrame;
        glutPostRedisplay();
    }
}

/*
 * Sets the alternate display function. The alternate display function is used
 * whenever normal gameplay needs to be interrupted.
 */
void graphics_set_draw_alt( void (*function)(void) ) {
    draw_alt = function;
}

/*
 * Swaps which display function is used.
 */
void graphics_swap_draw() {
    alternate_draw = !alternate_draw;
}

/*
 * Loads some of the textures.
 * TODO: This has probably got to go.
 */
void graphics_load_textures() {
    player_texture = texture_load_unit(
        luaR_getconstants("unitTextures", "player"));
    cat_texture = texture_load_unit(luaR_getconstants("unitTextures", "cat"));
}

/*
 * Initializes all of the OpenGL graphics used in the project.
 */
int graphics_init( int *argc, char **argv ) {
    int positionX;
    int positionY;
    fps = luaR_getconstanti("windowDefaults", "fps");
    window_width = luaR_getconstanti("windowDefaults", "width");
    window_height = luaR_getconstanti("windowDefaults", "height");
    positionX = luaR_getconstanti("windowDefaults", "positionX");
    positionY = luaR_getconstanti("windowDefaults", "positionY");
    glutInit(argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowPosition(positionX, positionY);
    glutInitWindowSize(window_width, window_height);
    glutCreateWindow("The Great Outdoors");
    glutReshapeFunc(reshape);
    glutDisplayFunc(display);
    glutIdleFunc(rgDoTurn);
    rgAddTurnCallback(nextAnimationFrame);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    graphics_load_textures();
    texture_init();
    texture_load_textures();
    return 0;
}
